﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace Nirvana
{
    [AddComponentMenu("Nirvana/UI/Layout/Flow Layout Group")]
    public class FlowLayoutGroup : LayoutGroup
    {
        public enum RowAnchor
        {
            Upper,
            Middle,
            Lower
        }

        [SerializeField]
        private Vector2 spacing;
        [SerializeField]
        private RowAnchor rowAlignment;

        /// <summary>
        /// 使用ContentSizeFitter组件时，约束的最大宽度，超过换行
        /// </summary>
        [Tooltip("Used working for ContentSizeFitter with horizontalFit")]
        [SerializeField]
        private float contentSizeFitterWidth;
        /// <summary>
        /// 使用ContentSizeFitter组件时，约束的最小宽度，控制root节点的rect宽度
        /// </summary>
        [SerializeField]
        private float minContentSizeFitterWidth;
        /// <summary>
        /// 指定对应行的padding距离
        /// </summary>
        [SerializeField]
        private float[] rowPadding;
        private ContentSizeFitter _fitter;
        private List<RectTransform> rectlist = new List<RectTransform>();

        private float curRowHeight;
        /// <summary>
        /// 当前父节点的rect宽度
        /// </summary>
        private float curRootWidth;

        public Vector2 Spacing
        {
            get
            {
                return this.spacing;
            }
        }

        public float ContentSizeFitterWidth
        {
            get
            {
                return this.contentSizeFitterWidth;
            }
        }

        /// <summary>
        /// 每行总宽带
        /// </summary>
        public float LastRowWidth { get; set; }

        private bool IsCenter()
        {
            return base.childAlignment == TextAnchor.LowerCenter || base.childAlignment == TextAnchor.MiddleCenter || base.childAlignment == TextAnchor.UpperCenter;
        }

        private bool IsRight()
        {
            return base.childAlignment == TextAnchor.LowerRight || base.childAlignment == TextAnchor.MiddleRight || base.childAlignment == TextAnchor.UpperRight;
        }

        private bool IsMiddle()
        {
            return base.childAlignment == TextAnchor.MiddleLeft || base.childAlignment == TextAnchor.MiddleRight || base.childAlignment == TextAnchor.MiddleCenter;
        }
        private bool IsLower()
        {
            return base.childAlignment == TextAnchor.LowerLeft || base.childAlignment == TextAnchor.LowerRight || base.childAlignment == TextAnchor.LowerCenter;
        }

        public override void CalculateLayoutInputHorizontal()
        {
            base.CalculateLayoutInputHorizontal();
            if(this._fitter == null)
            {
                this._fitter = base.GetComponent<ContentSizeFitter>();
            }
            bool IsHorFit = this._fitter != null && this._fitter.horizontalFit != ContentSizeFitter.FitMode.Unconstrained;
            if (IsHorFit) this.CalculateLayoutInputVertical();
            float width = this.GetMinWidth() + this.padding.left + this.padding.right;
            if (IsHorFit)
            {
                width = Mathf.Max(width, this.minContentSizeFitterWidth, this.curRootWidth);
            }
            base.SetLayoutInputForAxis(width, -1, -1, 0);
        }


        public override void CalculateLayoutInputVertical()
        {
            this.curRowHeight = this.SetLayoutInputVertical(1, true);
        }
        public override void SetLayoutHorizontal()
        {
            this.SetLayoutInputVertical(0, false);
        }

        public override void SetLayoutVertical()
        {
            this.SetLayoutInputVertical(1, false);
        }

        public void UpdateLayoutHorizontal()
        {
            this.SetLayoutInputVertical(0, true);
        }
        public void UpdateLayoutVertical()
        {
            this.SetLayoutInputVertical(1, true);
        }

        private float SetLayoutInputVertical(int axis,bool flag)
        {
            return this.SetLayoutInputVertical(axis, flag, this.rectChildren);
        }

        /// <summary>
        /// 计算布局的总宽度，每行最大高度，返回总高度
        /// </summary>
        /// <param name="axis"></param>
        /// <param name="flag"></param>
        /// <param name="rects"></param>
        /// <returns></returns>
        private float SetLayoutInputVertical(int axis, bool flag, List<RectTransform> rects)
        {
            float rootHeight = this.rectTransform.rect.height;
            if (this._fitter == null) this._fitter = this.GetComponent<ContentSizeFitter>();
            //节点父对象的宽度
            float rootWidth = 0;
            if (this._fitter != null && this._fitter.horizontalFit !=  ContentSizeFitter.FitMode.Unconstrained)
                rootWidth = this.contentSizeFitterWidth;
            else
            {
                rootWidth = this.rectTransform.rect.width - this.padding.left - this.padding.right;
            }

            float totalHeight = (!this.IsLower()) ? this.padding.top : this.padding.bottom;
            this.LastRowWidth = 0f;
            this.curRootWidth = 0;
            float totalWidth = 0, maxPreHight = 0;
            int paddingIndex = 0;
            for (int i = 0; i < rects.Count; i++)
            {
                int index = (!this.IsLower()) ? i : (rects.Count - 1 - i);
                var rect = rects[index];
                float preW = LayoutUtility.GetPreferredSize(rect, 0);
                float preH = LayoutUtility.GetPreferredSize(rect, 1);
                preW = Mathf.Min(preW, rootWidth);
                //换行
                if(totalWidth + preW > rootWidth)
                {
                    //退回一个space
                    totalWidth -= this.spacing.x;
                    if (!flag)
                    {
                        float h = this.GetHeight(rootHeight, totalHeight, maxPreHight);
                        this.SetLayoutInputVertical(this.rectlist, totalWidth, maxPreHight, rootWidth, this.padding.left, h, axis, paddingIndex++);

                    }
                    this.rectlist.Clear();
                    totalHeight += maxPreHight;
                    totalHeight += this.spacing.y;
                    //复位，下行开始重新计算
                    maxPreHight = 0;
                    totalWidth = 0;
                }
                totalWidth += preW;
                this.rectlist.Add(rect);
                if (preH > maxPreHight) maxPreHight = preH;
                if(i<rects.Count - 1)
                {
                    totalWidth += this.spacing.x;
                }
                if (this.curRootWidth < totalWidth) curRootWidth = totalWidth;
            }

            if (!flag)
            {
                float h = this.GetHeight(rootHeight, totalHeight, maxPreHight);
                totalWidth -= this.spacing.x;
                this.SetLayoutInputVertical(this.rectlist, totalWidth, maxPreHight, rootWidth - (this.rectlist.Count <= 1?0:this.spacing.x), this.padding.left, h, axis, paddingIndex);

            }
            this.rectlist.Clear();
            this.LastRowWidth = Mathf.Max(totalWidth, 0);
            totalHeight += maxPreHight;
            totalHeight += (!this.IsLower()) ? this.padding.bottom : this.padding.top;
            if(flag && axis == 1)
            {
                base.SetLayoutInputForAxis(totalHeight, totalHeight, -1, axis);
            }
            return totalHeight;
        }


        private float GetHeight(float rootHeight,float curHeight,float maxPreHight)
        {
            float height;
            if (this.IsLower()) height = rootHeight - curHeight - maxPreHight;
            else if (this.IsMiddle()) height = rootHeight * 0.5f - this.curRowHeight * 0.5f + curHeight;
            else height = curHeight;
            return height;
        }

        private void SetLayoutInputVertical(List<RectTransform> _list,float spacex,float spacey,float workwidth,float paddingleft,float paddingright,int axis,int paddingIndex)
        {
            if(this.rowPadding != null && this.rowPadding.Length > paddingIndex)
            {
                paddingleft += this.rowPadding[paddingIndex];
            }
            if (this.IsCenter())
            {
                paddingleft += (this.rectTransform.sizeDelta.x - spacex) * 0.5f;
            }
            else if (this.IsRight())
            {
                paddingleft += (this.rectTransform.sizeDelta.x - spacex);
            }
            for(int i = 0; i < _list.Count; i++)
            {
                int index = (!this.IsLower()) ? i : _list.Count - 1 - i;
                var rect = _list[index];
                float preW = LayoutUtility.GetPreferredSize(rect, 0);
                float preH = LayoutUtility.GetPreferredSize(rect, 1);
                preW = Mathf.Min(preW, workwidth);
                if(this.IsMiddle() || this.rowAlignment == RowAnchor.Middle)
                {
                    paddingright += (spacey - preH) * 0.5f;
                }else if(this.IsLower() || this.rowAlignment == RowAnchor.Lower)
                {
                    paddingright += spacey - preH;
                }

                if(axis == 0)
                {
                    base.SetChildAlongAxis(rect, 0, paddingleft, preW);
                }
                else
                {
                    base.SetChildAlongAxis(rect, 1, paddingright, preH);
                }

                if (i < _list.Count - 1) paddingleft += preW + this.spacing.x;
            }
        }
        /// <summary>
        /// 找出所有子对象布局中最大的MinWidth
        /// </summary>
        /// <returns></returns>
        private float GetMinWidth()
        {
            float width = 0;
            foreach(var t in this.rectChildren)
            {
                float minWidth = LayoutUtility.GetMinWidth(t);
                width = Mathf.Max(minWidth, width);
            }
            return width;
        }
    }
}

